package com.komon.object.response;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Map;

import javax.servlet.ServletOutputStream;

import com.komon.constant.Configuration;
import com.komon.object.CRLFWriter;

public abstract class ResponseOutputStream extends ServletOutputStream {
	protected final HttpResponse response;
	//root out put stream
	protected OutputStream socketOutputStream;
	//the writer to write the statusLine and headers and other meta message
	protected final CRLFWriter messageWriter;
	//this flag is recode whether the response body has been flushed
	protected volatile boolean isCommited = false;
	//this flag used to indicate whether the data has been written to buffer
	private volatile boolean isWritten = false;
	//buffer
	protected volatile int bufferSize = Configuration.DEFAULT_RESP_BUFFER_SIZE;
	protected volatile int bufferOffset = 0;
	protected byte[] buffer;
	//the flag show whether the buffer_size has been modified
	protected volatile boolean bufferUpdated = false;
	
	public ResponseOutputStream(HttpResponse response, OutputStream socketOutputStream) {
		this.response = response;
		//FIXME: default encoding?
		messageWriter =  new CRLFWriter(new OutputStreamWriter(socketOutputStream));
		this.socketOutputStream = socketOutputStream;
		buffer = new byte[bufferSize];
	}
	
	public void clearBuffer(){
		if (buffer != null) {
			buffer = new byte[bufferSize];
		}
	}
	
	/**
	 * to keep the sequence, here don't user real write, just write the data into buffer
	 * then, we can do real write for status-line and header when flush()
	 */
	public synchronized void write(int b) throws IOException {
		//lazy init the buffer to let user set the buffer size of servletResponse
		if (buffer == null){
			buffer = new byte[bufferSize];
		}
		//TODO: control the behavior of buffer
		if (bufferOffset +1 > bufferSize) {
			flush();
		} 
		buffer[bufferOffset++] = (byte) b;
		isWritten = true;
	}
	
	
	/* (non-Javadoc)
	 * @see java.io.OutputStream#flush()
	 */
	/**
	 * all different behaviors are focus on flush method when write the body
	 */
	@Override
	public void flush() throws IOException {
		//ensure write status-line and header at first time before write anything of body content
		if (!isCommited) {
			writeStatusLineAndHeader();
			messageWriter.flush();
			isCommited = true;
		}
		flushBody();
		updateBuffer();
	}
	
	protected void updateBuffer(){
		//be care that buffer is not full at all the time, here should use bufferOffset as the actual length
		bufferOffset = 0;
		if (bufferUpdated){
			buffer = new byte[bufferSize];
			bufferUpdated = false;
		}
	}
	
	/**
	 * let sub class override it to provide different behavior about transfer-encoding
	 * this method shoud only be called in flush()
	 * @throws IOException
	 */
	protected abstract void flushBody() throws IOException;
	
	public void writeStatusLineAndHeader( ) throws IOException {
		StringBuffer line = new StringBuffer();
		//status line
		line.append("HTTP/" + response.getStatusLine().getHttpVersion()).append(" ").
					append(response.getStatusLine().getStatusCode()).append(" ").
					append(response.getStatusLine().getReason());
		
		messageWriter.writeCRLFLine(line.toString());
		//headers
		for (Map.Entry<String, String> entry : response.getHeaders().entrySet()) {
			messageWriter.writeCRLFLine(entry.getKey() + ":" + entry.getValue());
		}
		messageWriter.writeCRLFLine("");
	}
	
	/**
	 * @return the isCommited
	 */
	public boolean isCommited() {
		return isCommited;
	}

	/**
	 * @return the isWritten
	 */
	public boolean isWritten() {
		return isWritten;
	}
	
	/**
	 * @return the bufferSize
	 */
	public int getBufferSize() {
		return bufferSize;
	}

	/**
	 * @param bufferSize the bufferSize to set
	 */
	public synchronized void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
		this.bufferUpdated = true;
	}
}
